/**
 * Copyright 2012 ibiblio
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0.txt
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.ibiblio.terasaur.item

import org.apache.solr.client.solrj.impl.CommonsHttpSolrServer
import org.apache.solr.client.solrj.SolrQuery
import org.apache.solr.client.solrj.response.QueryResponse
import org.apache.solr.common.SolrDocumentList
import org.apache.solr.common.SolrInputDocument
import org.ibiblio.terasaur.item.Item
import org.ibiblio.terasaur.search.SolrItem
import java.net.URLEncoder
import org.grails.plugins.sanitizer.MarkupSanitizerResult
import org.grails.plugins.sanitizer.MarkupSanitizerService
import org.apache.solr.client.solrj.SolrServerException
import org.ibiblio.terasaur.search.LuceneUtils
import org.ibiblio.terasaur.search.DateHelper

class SearchService {
    static transactional = true
    def grailsApplication // injected
    def markupSanitizerService // injected

    static URL_ENCODING = 'UTF-8'

    /**
     * Execute query of solr item index.  Valid input query params:
     *  - query: general search query, subject to validation and scrubbing. Use
     *      this for user-generated input.
     *  - rawQuery: query string passed through to Solr without alteration.  Use
     *      this for system- or developer-generated queries.
     *  - offset: item offset, use for paging
     *  - max: result set max/limit, use for paging
     *  - sort: list of maps ([field:<sort field>, order:<sort order>]). e.g.
     *      [[field:'date_created', order:SolrQuery.ORDER.desc]]
     *
     * @param params
     * @return
     */
    public Map query(Map params) {
        String errorCode = ''
        String qString
        SolrDocumentList results
        List itemList

        try {
            if (params.rawQuery) {
                qString = params.rawQuery
            } else {
                qString = _getCleanedQString(params.query)
            }

            if (qString) {
                SolrQuery query = _getSolrQuery(qString, params)
                _addFilters(query, params)
                results = _executeSolrQuery(query)
                itemList = _getSolrItemList(results)
            }
        } catch (org.apache.solr.client.solrj.SolrServerException e) {
            errorCode = 'item.search.unavailable.message'
        } catch (Exception e) {
            log.error("SearchService::query exception", e)
        }

        Map m = [
            'documentList': results,
            'itemList': itemList,
            'error': errorCode,
            'query': qString
            ]
        return m
    }

    private String _getCleanedQString(String qStringParam) {
        MarkupSanitizerResult result = markupSanitizerService.sanitize(qStringParam)
        if (result.isInvalidMarkup()) {
            log.info("Got invalid markup in search query: " + result.errorMessages)
        }
        String qString = LuceneUtils.cleanQuery(result.cleanString)
        return qString
    }

    private SolrQuery _getSolrQuery(String qString, Map params) {
        SolrQuery query = new SolrQuery()
        query.setQuery(qString)
        query.addFilterQuery('published_t:[* TO NOW]')
        query.addFilterQuery('deleted:0')

        if (params.sort) {
            params.sort.each {
                query.addSortField(it.field, it.order)
            }
        } else {
            query.addSortField('published', SolrQuery.ORDER.desc)
        }
        query.setParam('start', params.offset.toString())
        query.setParam('rows', params.max.toString())
        return query
    }

    private SolrDocumentList _executeSolrQuery(SolrQuery query) {
        CommonsHttpSolrServer server = _getSolrServer()
        QueryResponse rsp = server.query(query)
        SolrDocumentList results = rsp.getResults()
        return results
    }

    private List _getSolrItemList(SolrDocumentList results) {
        List itemList = new ArrayList()
        results.each { itemList.add(new SolrItem(it)) }
        return itemList
    }

    private void _addFilters(SolrQuery query, Map params) {
        if (!params.filters) {
            return
        }
        params.filters.each {
            query.addFilterQuery(it)
        }
    }

    /**
     * Add or update an item in the index
     * @param item
     */
    public void add(Item item) {
        try {
            SolrInputDocument doc = _getSolrInputDocument(item)
            CommonsHttpSolrServer server = _getSolrServer()
            server.add(doc)
            server.commit()
        } catch (ConnectException e) {
            log.error('Search service connection error while updating item ' + item.id)
        } catch (Exception e) {
            log.error(e)
        }
    }

    private SolrInputDocument _getSolrInputDocument(Item item) {
        SolrInputDocument doc = new SolrInputDocument()
        def fmt = DateHelper.getISODateFormatter()

        doc.addField('id', item.id)
        doc.addField('type', item.type)
        doc.addField('title', item.title)
        doc.addField('description', item.description)
        doc.addField('language', item.language)
        doc.addField('license', item.license)
        doc.addField('published', item.published.toString(fmt))
        doc.addField('owner_id', item.owner.id)
        doc.addField('owner_nickname', item.owner.username)
        doc.addField('deleted', item.deleted)
        doc.addField('collection_id', item.collection.id)
        doc.addField('collection_name', item.collection.name)
        doc.addField('date_created', item.dateCreated.toString(fmt))
        doc.addField('last_updated', item.lastUpdated.toString(fmt))

        def torrents = []
        Long torrentTotalSize = 0
        item.torrents.each {torrent->
            torrentTotalSize += torrent.size
            def torrentInfo = torrent.infoHash + ',' + torrent.size + ',' + torrent.published.toString(fmt)
            torrents << torrentInfo
        }

        doc.addField('torrent_count', item.torrents ? item.torrents.size() : 0)
        doc.addField('torrent_total_size', torrentTotalSize)
        doc.addField('torrents', torrents)
        return doc
    }

    private CommonsHttpSolrServer _getSolrServer() {
        String url = grailsApplication.config.terasaur.web.solr.url
        def server = new CommonsHttpSolrServer(url)
        return server
    }
}
